package edu.cmu.cs211.exprtree.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.math.BigDecimal;
import java.util.Map;
import java.util.HashMap;

import org.junit.Test;

import edu.cmu.cs211.exprtree.ast.BinaryExpression;
import edu.cmu.cs211.exprtree.ast.ConstantExpression;
import edu.cmu.cs211.exprtree.ast.Expression;

public abstract class BinaryExpressionTest extends ExpressionTestUtil {

	public abstract BinaryExpression createExpr(Expression l, Expression r);

	@Test
	public void testConstructor() {
		BinaryExpression x = createExpr(ConstantExpression.ONE, ConstantExpression.ZERO);
		assertEquals("left", ConstantExpression.ONE, x.getLeft());
		assertEquals("right", ConstantExpression.ZERO, x.getRight());
	}

	@Test(expected = NullPointerException.class)
	public void testConstructorLeftNull() {
		createExpr(null, ConstantExpression.ZERO);
	}

	@Test(expected = NullPointerException.class)
	public void testConstructorRightNull() {
		createExpr(ConstantExpression.ZERO, null);
	}

	public void toStringTest(String operand) {
		BinaryExpression x1 = createExpr(ConstantExpression.ONE, ConstantExpression.ZERO);
		BinaryExpression x2 = createExpr(ConstantExpression.ONE, createExpr(
				ConstantExpression.ZERO, ConstantExpression.MINUS_ONE));
		BinaryExpression x3 = createExpr(
				createExpr(ConstantExpression.ONE, ConstantExpression.ZERO),
				ConstantExpression.MINUS_ONE);
		char symbol = x1.getOperatorSymbol();
		String one = ConstantExpression.ONE.toString();
		String zero = ConstantExpression.ZERO.toString();
		String minus_one = ConstantExpression.MINUS_ONE.toString();

		assertEquals("toString incorrect", String.format("(%s%s%s)", one, symbol, zero), x1
				.toString());
		assertEquals("toString incorrect", String.format("(%s%s(%s%s%s))", one, symbol, zero,
				symbol, minus_one), x2.toString());
		assertEquals("toString incorrect", String.format("((%s%s%s)%s%s)", one, symbol, zero,
				symbol, minus_one), x3.toString());
	}

	protected void doMockExpressionTest() {
		ConstantExpression cfive = new ConstantExpression(5);
		ConstantExpression csix = new ConstantExpression(6);

		Expression five = new MockConstantExpression(5);
		Expression six = new MockConstantExpression(6);

		assertEquals("Constant folding with sub-expression that "
				+ "does not derive from BinaryExpression, UnaryExpression"
				+ "ConstantExpression or VariableExpression.", createExpr(cfive, csix).cfold(),
				createExpr(five, six).cfold());

	}

	@Test
	public void doMockEvaluateTest() {
		final Map<String, BigDecimal> variableMap = new HashMap<String, BigDecimal>();

		ConstantExpression cfive = new ConstantExpression(5);
		ConstantExpression csix = new ConstantExpression(6);

		Expression five = new MockConstantExpression(5) {
			public BigDecimal evaluate(Map<String, BigDecimal> m) {
				// we want == here, not .equals, we're looking for object identity
				assertTrue("Didn't pass the variable map along in evaluate", m == variableMap);			
				return super.evaluate(m);
			}
		};

		Expression six = new MockConstantExpression(6) {
			public BigDecimal evaluate(Map<String, BigDecimal> m) {
				// we want == here, not .equals, we're looking for object identity
				assertTrue("Didn't pass the variable map along in evaluate", m == variableMap);			
				return super.evaluate(m);
			}
		};

		assertEquals("Evaluating with sub-expression that "
				+ "does not derive from BinaryExpression, UnaryExpression"
				+ "ConstantExpression or VariableExpression.", createExpr(cfive, csix).evaluate(variableMap),
				createExpr(five, six).evaluate(variableMap));

	}
}
